home *** CD-ROM | disk | FTP | other *** search
/ Delphi Developer's Kit 1996 / Delphi Developer's Kit 1996.iso / power / commad / compnent.dph < prev   
Encoding:
Text File  |  1995-12-22  |  14.8 KB  |  256 lines

  1. Components
  2.  
  3. If youÆve had any exposure to BorlandÆs amazing new product, Delphi, you have no doubt noticed 
  4. the minimal amount of code required to write a fully functional application.  This is thanks to
  5. the Visual Component Library (VCL), which contains the components necessary to create an 
  6. application containing buttons, list boxes, and so on simply by 'drawing' them on a form at 
  7. design time.  VCL components are a significant improvement for three reasons:
  8.  
  9. The component code is linked right into your application, which means that you do not have 
  10. to include seemingly endless custom control files when you ship. However, this does not mean 
  11. that VBX users are left in the cold -- Delphi contains full support for VBXÆs that meet VB 1.0 
  12. specifications.
  13.  
  14. Delphi has the ability to create custom components as well by simply creating a descendant 
  15. object of a template component (TComponent) or one of its descendants.     This is actually a 
  16. powerful concept if you think about it for a moment; it means that you can derive your component 
  17. from a fully functional one in a clean, no nonsense fashion. For example, you could derive a 
  18. component from TStringGrid and add in-cell editing, then save it as a separate component.
  19.  
  20. If all this werenÆt enough, youÆll be pleased to learn that you can debug a component, while 
  21. an application that uses that component is running, by using DelphiÆs integrated debugger.
  22.  
  23. In Delphi, there are two types of components: visual and non-visual. Visual components do 
  24. something that you can see and often interact with. Non-visual components, on the other hand,
  25. encapsulate a task that has no necessary involvement with the screen at runtime, like run a 
  26. timer, interface with a database, or in our case, implement a simple communications interface. 
  27. You might wonder what the point of using a visual programming language with a non-visual control 
  28. is, and why not just create a unit that contains the same code and place it in your USES 
  29. statement.  At runtime, the difference would be zero, but during your design phase, you wouldnÆt 
  30. get the ability to visually change default values, automatically create procedures for events by 
  31. double clicking on their names, or see at a glance exactly what is contained in your application 
  32. -- that is the beauty of a fully visual design environment.
  33.  
  34.  
  35. Creating a Component
  36.  
  37. To begin the process of creating a component, a unit must be created, a parent component must be 
  38. decided upon, and the component must have a register procedure.  Every custom component created
  39. in Delphi must be derived from TComponent or a descendant of it.  Since we do not need graphical 
  40. or user interface abilities, we can stick with deriving our object from TComponent.  All of this 
  41. is done like so:
  42.  
  43. unit Comm;
  44.  
  45. interface
  46.  
  47. uses Classes;
  48.  
  49. type
  50.   TComm=class(TComponent)
  51.         end;
  52.  
  53. procedure Register;
  54.  
  55. implementation
  56.  
  57. procedure Register;
  58. begin
  59.   RegisterComponents ('Additional',[TComm]);
  60. end;
  61.  
  62. end.
  63.  
  64. If you were to compile the above example and add it to the component library, you might be 
  65. surprised when you found out that it is fully functional.  For example, it can be added to a
  66. form, its position can be changed, and it has properties that can be changed and will stay
  67. changed when you reload the form.  True, it canÆt do anything useful, but you have to start
  68. somewhere.
  69.  
  70. Now that we have created the skeleton for the component, we can begin to flesh it out.
  71. One of the first things to be decided upon is properties.  Properties for components are usually 
  72. added to the published section of the class declaration.  This allows the property to be 
  73. accessed at design time. A typical property declaration looks like this:
  74.  
  75. property Port:Byte 
  76.   read FPort  
  77.   write SetPort 
  78. default 1;
  79.  
  80. This indicates that Port is of type Byte.  But, what is all that funny stuff after the
  81. declaration?  That is a clever way to allow some procedure to be called when you set the value
  82. of a variable.  The read word indicates that a variable called FPort is to be used when reading 
  83. the value of Port.  The write word indicates that the procedure SetPort is to be called when 
  84. setting the value of Port.  The write procedure for a property always has exactly one variable 
  85. of the same type as the property itself, and usually, the variable that the corresponding 
  86. property represents is set within it.  If it had not been necessary to call a procedure when the 
  87. value of Port was set, the declaration could have been rewritten as follows:
  88.  
  89. property Port:Byte 
  90.   read FPort
  91.   write FPort 
  92. default tptNone;
  93.  
  94. The write section now indicates that the variable FPort will be accessed for writes as 
  95. well as reads.  This brings up an important point -- properties are not variables -- they are 
  96. access mechanisms for getting and setting values.
  97.  
  98. Notice the default word in the above statements.  The value after it is the same one that 
  99. appears in the object inspector initially at design time, before you change anything.  Even 
  100. though this default value scheme exists, it is still very important to always initialize the 
  101. variable or call the access function for the property with the same default value during your 
  102. Create constructor.  If you fail to do so, the values you set may not be the values that are 
  103. actually used because the compiler checks to see if the value that is entered in the object 
  104. inspector and stored with the form is the same as the default value.  If it is the actual value, 
  105. it is not set because it was assumed to have been set in the Create constructor.  If this reason 
  106. is not compelling enough, you should be aware that if you create a component at run time, the 
  107. values indicated by the default directive are not used at all!  The only initialization that 
  108. will be done is that which occurred in the Create constructor.  One final hazard concerning the 
  109. Create constructor should be noted; always declare it with the override word, otherwise it will 
  110. never be called at all.  The reason you should be on alert for this is if you have been 
  111. programming in Borland Pascal for any length of time, it is probably etched within your head 
  112. that constructors can never be virtual, so you automatically hit Enter at the end of the line 
  113. without a second thought.
  114.  
  115.  
  116. Events and Their Handlers
  117.  
  118. Now that properties are taken care of, we can go on to the events. Events are properties that 
  119. access pointers to functions.  Consider the following declaration:
  120.  
  121. property OnReceive:TNotifyReceiveEvent 
  122.   read FOnReceive
  123. write FOnReceive;
  124.  
  125. This is similar to what we have seen before, except the return type is TNotifyReceiveEvent.
  126. One of the changes to DelphiÆs implementation of Pascal is that a function can now return nearly 
  127. any type.  The type declaration for TNotifyEvent is shown below:
  128.  
  129. TNotifyReceiveEvent = procedure (Sender:TObject;Count:Word) of object;
  130.  
  131. You probably recognize this as a standard function template.  What you may not recognize is the 
  132. of object part.  All this does is indicate to the compiler that this function will be a member 
  133. of an object rather than a far function.
  134.  
  135. When the OnReceive property is read from or written to, what is really happening is that the 
  136. pointer to a procedure is being accessed.  When a form is created during run time, the setting 
  137. of this pointer happens automatically.  Calling the event function is now a piece of cake; all 
  138. you have to do is call the underlying member of the property, FOnReceive, in our example.
  139.  
  140.  
  141. Implementing Communications
  142.  
  143. The specification for the communications component is rather simple.  We need to be able to 
  144. assign the properties at both run-time and design-time, read and write to the comm port, and 
  145. respond to events, such as when the receive buffer has a certain amount of data and when the 
  146. amount of data in the transmit buffer drops below a certain point, making it ready to receive 
  147. more.  The code for the communication component is shown in Listing 1.
  148.  
  149. New properties include: baud rate, number of data bits, parity, communications port, 
  150. read buffer size, a value to indicate how full the receive buffer has to be before an event is 
  151. triggered, number of stop bits, a value to indicate how empty the transmit buffer has to be 
  152. before an event is triggered, and the write buffer size.  All of these properties can be set at 
  153. run time by clicking on the appropriate cell in the object inspector and selecting the 
  154. appropriate value.
  155.  
  156. Perhaps the most important property is Port, which defaults to tptNone because generally 
  157. you do not know what port you will be using at design time.  However, if youÆre doing a quick 
  158. test program, you can set the port value using the object inspector and proceed immediately in 
  159. your program to use the communications component without any runtime setup whatsoever.  By 
  160. setting the value of Port, what you are really doing is calling the method SetPort, which is 
  161. ultimately responsible for opening a port and closing a previously open one if an open port 
  162. exists.
  163.  
  164. Once the port has been opened, SetPort configures it to the proper state by setting all of the 
  165. appropriate properties such as baud rate and parity.  The code that sets these properties 
  166. is not the most efficient in the world because it calls GetCommState and SetCommState for each 
  167. property instead of calling GetCommState, setting all of the properties and then calling 
  168. SetCommState once.  But because this is done only once during the initialization of the port, it 
  169. was not a major concern, and I decided to keep it the way it is.  However, with a few upgrades 
  170. to the code, you can reduce the intitialization time considerably.
  171.  
  172. The final step in the call to SetPort is to enable communications event posting by calling 
  173. EnableCommNot-ification.  This results in the posting of three distinct events to a window: 
  174. CN_EVENT, CN_RECEIVE, and CN_TRANSMIT.  This, of course, means that we need a window handle to 
  175. post these events to.  AllocateHWnd is simple function for creating a hidden window.  It takes 
  176. a window procedure as a parameter and returns a window handle.  It is the window procedure that 
  177. receives the communications notifications and, in turn, calls the appropriate event handler.
  178.  
  179. The OnReceive and OnTransmit event handlers are straightforward.  They are called when 
  180. CN_RECEIVE and CN_TRANSMIT are posted to the window respectively.  The only other processing 
  181. that occurs before the actual event is triggered is to determine how many bytes are in the 
  182. appropriate buffer so the count can be included as a parameter.
  183.  
  184. The OnEvent event is not quite so straightforward.  When the property Events is set, thereby 
  185. calling the procedure SetEvents, a bit mask of events is created.  It is this bit mask that is 
  186. used in the call to SetCommEventMask to enable the desired events.  During the receipt of a 
  187. CN_EVENT, the events that occurred are retrieved and reset by calling GetCommEventMask.  These 
  188. events are added to a set which is then passed to the OnEvent event handler.
  189.  
  190. To write data to a port, you must call the Write method:
  191.  
  192. procedure TComm.Write(Data:PChar;Len:Word);
  193.  
  194. Here, Data is a pointer to a block of data, and Len is the length of the block.  Reading data is 
  195. done similarly:
  196.  
  197. procedure TComm.Read(Data:PChar;Len:Word);
  198.  
  199. The parameters are the same as before.  Remember that the value that you use for Len can be 
  200. determined from the Count parameter provided by the OnReceive event.
  201.  
  202. Error handling is minimal at best. Exceptions are not used, but rather a member function 
  203. -- IsError -- returns True when an error state occurrs.  Calling IsError also resets the error 
  204. state.  There are only three cases when IsError will return True: In trying to open a port, in 
  205. reading from the port, and in writing to the port.
  206.  
  207.  
  208. Initializing By the Numbers
  209.  
  210. When designing a component, the initialization sequence is important to keep in mind.  At first 
  211. I had the initial call to SetPort in the Create method, but a problem quickly showed up: Only 
  212. the property values set in the Create method would be used, rather than the expected values that 
  213. were set using the Object Inspector during design time.  This is because design time properties 
  214. are set after the Create method.
  215.  
  216. The Loaded method, on the other hand, provided what I was really looking for because it is not 
  217. called until all Object Inspector properties and events are set.  This, unfortunately, created 
  218. another minor problem: SetPort would be called twice, once during the automatic setting of all 
  219. the properties when the form was loaded, and again during my call in Loaded.  The problem is not 
  220. strictly that the same function would be called twice without just cause, but that the first 
  221. call to SetPort would be called at some unknown (at least to me) time.  This meant that none, 
  222. some, or all of the other properties would have been set and their write procedures called 
  223. after SetPort was called.  This can get messy if you consider that properties like 
  224. WriteBufferSize and ReadBufferSize work by actually closing the port and reopening it.  This fix 
  225. was simple: A flag, HasBeenLoaded, is set to False during the Create method and set to True 
  226. during the Loaded method.  When SetPort is called, it checks the flag to make sure it is True 
  227. before allowing the port to open.
  228.  
  229.  
  230. Adding the Component
  231.  
  232. To add the communications component to the Component Palette, use the Install Components dialog 
  233. box.  A palette bitmap (see Figure 1) was designed for the component and will be displayed on 
  234. the Component Palette(see Figure 2) in the ôAdditionalö section, providing the installation was 
  235. successful.
  236.  
  237.  
  238. Our Communications Example
  239.  
  240. The example program in Listing 2 and Listing 3 is a very simple terminal program that sends out 
  241. what you type and displays what is being received.  The form for the program (Listing 4 and 
  242. Figure 3), consists of a memo box component and the communications component.  To make the 
  243. response to the received data snappy, the RxFull count property is set to one.  The port number 
  244. has been set at design time, and in my case it is one.  If you try out this example, be sure to 
  245. set this number to a port that works for you.  Examination of the example programÆs code will 
  246. reveal that it contains a mere 43 lines of Object Pascal.  This is, of course, due to the 
  247. communications component.
  248.  
  249. In a nutshell, weÆve seen how a Delphi component goes together and how such a component can 
  250. reduce the amount of code in an application, while being ready for immediate re-use by anyone 
  251. who wishes to pull it down from the component palette.  By creating components such as this one, 
  252. we are in essence programming portions not only of our current projects, but of our future 
  253. projects as well.  Using this type of programming, you can quickly build large libraries of 
  254. components that will allow sophisticated projects to be assembled in hours compared to the days, 
  255. weeks, and perhaps even months they would take without them and without Delphi.
  256.